package com.mindata.blockchain.common;

import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by gs36278 on 2019/9/10.
 */
public class HttpClientHelper {
    private final static Logger logger = LoggerFactory.getLogger(HttpClientHelper.class);
    private static final String contentType = "application/json;charset=utf-8";
    private static final String charset = "UTF-8";
    private static final Object lockObj4HttpClient = new Object();
    private static final Object lockObj4PoolMax = new Object();
    private static final Object lockObj4Timeout = new Object();
    private static volatile CloseableHttpClient httpClient = null;
    private static volatile int Timeout = 3000;
    private static volatile int PoolMax = 3000;
    private static volatile PoolingHttpClientConnectionManager connectionManager = null;
    private static volatile RequestConfig requestConfig = null;
    private static volatile ConnectionKeepAliveStrategy connectionKeepAliveStrategy = null;

    static {
        try {
            PoolMax = 3000;
            Timeout = 3000;
            connectionManager = getConnectionManager(PoolMax);
            requestConfig = getRequestConfig(Timeout);
            connectionKeepAliveStrategy = getConnectionKeepAliveStrategy();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private static ConnectionKeepAliveStrategy getConnectionKeepAliveStrategy() {
        ConnectionKeepAliveStrategy myStrategy = (response, context) -> {
            HeaderElementIterator it = new BasicHeaderElementIterator
                    (response.headerIterator(HTTP.CONN_KEEP_ALIVE));
            while (it.hasNext()) {
                HeaderElement he = it.nextElement();
                String param = he.getName();
                String value = he.getValue();
                if (value != null && param.equalsIgnoreCase
                        ("timeout")) {
                    return Long.parseLong(value) * 1000;
                }
            }
            return 5 * 60 * 1000;//如果没有约定，则默认定义时长为5min
        };
        return myStrategy;
    }

    private static PoolingHttpClientConnectionManager getConnectionManager(int poolMax) {
        try {
            PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
            cm.setMaxTotal(100);
            cm.setDefaultMaxPerRoute(20);
            cm.setValidateAfterInactivity(1000);
            cm.setDefaultSocketConfig(
                    SocketConfig.custom()
                            .setSoKeepAlive(true)
                            .setTcpNoDelay(true)
                            .setSoReuseAddress(true)
                            .build()
            );
            return cm;
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    private static RequestConfig getRequestConfig(int timeout) {
        try {
            return RequestConfig.custom()
                    .setConnectionRequestTimeout(timeout)
                    .setSocketTimeout(timeout)
                    .setConnectTimeout(timeout)
                    .setExpectContinueEnabled(false)
                    .build();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }

    public static String GetString(String requestUri) {
        return GetString(requestUri, Timeout);
    }

    public static String GetString(String requestUri, int timeout) {
        return GetString(requestUri, timeout, PoolMax);
    }

    public static String GetString(String requestUri, int timeout, int poolMax) {
        HttpGet httpPost = new HttpGet(requestUri);
        String responseEntity = getResponseString(timeout, poolMax, httpPost);
        return responseEntity;
    }

    public static String PostString(String requestUri, String data) {
        return PostString(requestUri, data, null);
    }

    public static String PostString(String requestUri, String data, Map<String, String> map) {
        return PostString(requestUri, data, map, Timeout);
    }

    public static String PostString(String requestUri, String data, Map<String, String> map, int timeout) {
        return PostString(requestUri, data, map, timeout, PoolMax);
    }

    public static String PostString(String requestUri, String data, Map<String, String> map, int timeout, int poolMax) {
        HttpPost httpPost = new HttpPost(requestUri);
        StringEntity entity = new StringEntity(data, charset);
        entity.setContentType("application/json");
        entity.setContentEncoding("UTF-8");
        httpPost.setEntity(entity);
        httpPost.setHeader("content-type", contentType);
        if (map != null && !map.isEmpty()) {
            for (Map.Entry<String, String> entry : map.entrySet()) {
                httpPost.setHeader(entry.getKey(), entry.getValue());
            }
        }
        long start = System.currentTimeMillis();
        String responseEntity = getResponseString(timeout, poolMax, httpPost);
        long end = System.currentTimeMillis();
        StringBuilder httpRequestLogBuilder = new StringBuilder();
        httpRequestLogBuilder
                .append("url ").append(requestUri)
                .append("context ").append(data)
                .append("header ").append(map == null ? "null":map.toString())
                .append("result ").append(responseEntity)
                .append("cost ").append(end-start);
        return responseEntity;
    }

    public static String PostParams(String requestUri, Map<String, String> data) {
        return PostParams(requestUri, data, null);
    }

    public static String PostParams(String requestUri, Map<String, String> data, Map<String, String> map) {
        return PostParams(requestUri, data, map, Timeout);
    }

    public static String PostParams(String requestUri, Map<String, String> data, Map<String, String> map, int timeout) {
        return PostParams(requestUri, data, map, timeout, PoolMax);
    }

    public static String PostParams(String requestUri, Map<String, String> data, Map<String, String> map, int timeout, int poolMax) {
        String responseEntity = "";

        try {
            HttpPost httpPost = new HttpPost(requestUri);

            if (data != null && !data.isEmpty()) {
                //创建参数集合
                List<BasicNameValuePair> list = new ArrayList<>();
                for (Map.Entry<String, String> entry : data.entrySet()) {
                    list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                httpPost.setEntity(new UrlEncodedFormEntity(list, charset));
            }

            if (map != null && !map.isEmpty()) {
                for (Map.Entry<String, String> entry : map.entrySet()) {
                    httpPost.setHeader(entry.getKey(), entry.getValue());
                }
            }

            long start = System.currentTimeMillis();
            responseEntity = getResponseString(timeout, poolMax, httpPost);
            long end = System.currentTimeMillis();
            StringBuilder httpRequestLogBuilder = new StringBuilder();
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return responseEntity;
    }

    private static String getResponseString(int timeout, int poolMax, HttpRequestBase httpRequest) {

        CloseableHttpResponse response = null;
        CloseableHttpClient httpClient = getHttpClientInstance(timeout, poolMax);
        try {
            response = httpClient.execute(httpRequest);
            HttpEntity responseEntity = response.getEntity();
            if (responseEntity != null) {
                return EntityUtils.toString(responseEntity, charset);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != response) {
                try {
                    EntityUtils.consume(response.getEntity());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
        return null;
    }

    private static CloseableHttpClient getHttpClientInstance(int timeout, int poolMax) {
        if (httpClient == null) {
            synchronized (lockObj4HttpClient) {
                httpClient = getHttpClient(timeout, poolMax);
            }
        }
        return httpClient;
    }

    private static CloseableHttpClient getHttpClient(int timeout, int poolMax) {
        int pool = 3000;
        if (pool != PoolMax) {
            synchronized (lockObj4PoolMax) {
                PoolMax = pool;
                connectionManager = getConnectionManager(PoolMax);
            }
        }
        int time = 3000;
        if (time != Timeout) {
            synchronized (lockObj4Timeout) {
                Timeout = time;
                requestConfig = getRequestConfig(time);
            }
        }

        return HttpClientBuilder.create()
                .setDefaultRequestConfig(requestConfig)
                .setConnectionManager(connectionManager)
                .setKeepAliveStrategy(connectionKeepAliveStrategy)
                .setRetryHandler(new DefaultHttpRequestRetryHandler(0, false))
                .setConnectionManagerShared(true)
                .build();
    }
}
